package com.jie.common.http.entity;

import cn.hutool.core.text.StrBuilder;
import com.jie.common.http.constant.HttpCharacterConstant;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 首部行
 * @author XieZhiJie
 * @date 2022/04/21 16:25
 */
public class HttpHeaderLine {

    /**
     * 首部行的合法的字符集合
     */
    private static final Set<Character> VALID_CHARACTER_SET = new HashSet<>(16);

    static {
        // 初始化合法字符集
        initValidCharacterSet();
    }

    /**
     * 记录首部行的键值对的map
     */
    private final Map<String, String> headerLineMap = new HashMap<>(16);

    /**
     * 初始化首部行的合法字符集
     */
    public static void initValidCharacterSet() {
        // 初始化首部行的键值对的合法字符
        int letterCount = 26;
        for (int i = 0; i < letterCount; i++) {
            VALID_CHARACTER_SET.add((char) ('a' + i));
            VALID_CHARACTER_SET.add((char) ('A' + i));
        }
        int numberCount = 10;
        for (int i = 0; i < numberCount; i++) {
            VALID_CHARACTER_SET.add((char) ('0' + i));
        }
        VALID_CHARACTER_SET.add('_');
        VALID_CHARACTER_SET.add('.');
        VALID_CHARACTER_SET.add('=');
        VALID_CHARACTER_SET.add('-');
        VALID_CHARACTER_SET.add('/');
    }

    /**
     * 判断ch是否合法
     * @param ch ch
     * @return   不合法则返回true
     */
    public static boolean isNotValidCharacter(char ch) {
        return !VALID_CHARACTER_SET.contains(ch);
    }

    /**
     * 返回键值对map
     * @return 键值对map
     */
    public Map<String, String> getHeaderLineMap() {
        return this.headerLineMap;
    }

    /**
     * 添加一个键值对到请求行
     * @param property 参数名
     * @param value    参数值
     * @return         返回是否添加成功
     */
    public boolean addOneHeaderLine(String property, String value) {
        // 判断字符是否合法
        int length = property.length();
        for (int i = 0; i < length; i++) {
            if (HttpHeaderLine.isNotValidCharacter(property.charAt(i))) {
                return false;
            }
        }
        length = value.length();
        for (int i = 0; i < length; i++) {
            if (HttpHeaderLine.isNotValidCharacter(value.charAt(i))) {
                return false;
            }
        }
        this.headerLineMap.put(property, value);
        return true;
    }

    /**
     * 将当前的首部头里的键值对转为字符串
     * @return 返回字符串
     */
    public String getMapString() {
        StrBuilder sb = new StrBuilder(16);
        for (Map.Entry<String, String> entry : this.headerLineMap.entrySet()) {
            sb.append(entry.getKey()).append(':').append(' ').append(entry.getValue()).append("\r\n");
        }
        // 使用UTF8转为字节数组后能和ascii兼容, 遍历bytes的时候, 遇到如\r\n之类的, 它们不会作为utf8字符的一部分, 因此不需要担心
        return sb.append("\r\n").toString();
    }

    /**
     * 将当前首部行转为字节数组
     * @return 返回转换后的字节数组
     */
    public byte[] getBytes() {
        return getMapString().getBytes(StandardCharsets.UTF_8);
    }

    /**
     * 从bytes[bytesIndex]遍历起, 直到遇到两个\r\n则停止
     * 将得到的每个键值对加入到首部行中
     * 这里假定每个输入都符合格式
     * @param bytes      字节数组
     * @param bytesIndex 开始遍历索引
     * @return           返回结束遍历索引
     */
    public int addParamToMap(byte[] bytes, int bytesIndex) {
        char eof = HttpCharacterConstant.LINE_FEED;
        char sep = HttpCharacterConstant.HEADER_SEPARATE;
        byte b = bytes[bytesIndex];
        while (b != eof) {
            int startIndex = bytesIndex;
            while (b != sep) {
                b = bytes[++bytesIndex];
            }
            String paramName = new String(bytes, startIndex, bytesIndex - startIndex);
            // 跳过": "
            bytesIndex += 2;
            b = bytes[bytesIndex];
            startIndex = bytesIndex;
            while (b != eof) {
                b = bytes[++bytesIndex];
            }
            addOneHeaderLine(paramName, new String(bytes, startIndex, bytesIndex - startIndex));
            // 跳过"\r\n"
            bytesIndex += 2;
            b = bytes[bytesIndex];
        }
        return bytesIndex;
    }

    @Override
    public String toString() {
        return getMapString();
    }
}
